
/**
\file
Defines the rainbow::blend_state class.
\author Sean Tromans
*/

#include <sun_pre_compiled.hpp>
#include <rainbow/graphics_device/blend_state/blend_state.hpp>

#if !defined(SUN_DISABLE_LUA)
#include <sun/lua/lua.hpp>
#endif

namespace rainbow
{

	namespace channel_mask
	{

		namespace detail
		{

			/** Dummy class to contain the enumerable values for the channel masks. */
			struct dummy_channel_enum_container { };

		}

		/** Sets a specific channel in a colour mask. */
		void set_channel( std::uint8_t & mask, bool active, enum_ channel )
		{
			if ( active )
			{
				mask |= channel;
			}
			else
			{
				mask &= ~channel;
			}
		}

		/** Creates a colour mask from 4 flags indicating which channels should be active. */
		std::uint8_t colour_mask( bool r, bool g, bool b, bool a )
		{
			std::uint8_t mask = 0;
			set_channel( mask, r, red );
			set_channel( mask, g, green );
			set_channel( mask, b, blue );
			set_channel( mask, a, alpha );
			return mask;
		}

		/** Tests if a channel in a specific colour mask is set. */
		bool is_channel_active( std::uint8_t mask, enum_ channel )
		{
			return ( mask & channel ) == channel;
		}
	}

	/**
	Default Constructor.
	\author Sean Tromans
	*/
	blend_state::blend_state( )
		:		enabled(false)
			,	alpha_to_coverage_enabled(false)

			,	src_colour(GL_ONE)
			,	dst_colour(GL_ZERO)

			,	src_alpha(GL_ONE)
			,	dst_alpha(GL_ZERO)

			,	colour_equation(GL_ADD)
			,	alpha_equation(GL_ADD)

			,	colour_mask( channel_mask::colour_mask( true, true, true, true ) )
	{
	}

	/**
	Applies any changes from the provided blend_state to OpenGL.
	@param other The currently active blend_state.
	\author Sean Tromans
	*/
	void blend_state::apply( blend_state const & other ) const
	{
		if ( enabled != other.enabled )
		{
			sun::detail::set_state( GL_BLEND, enabled );
		}

		if ( alpha_to_coverage_enabled != other.alpha_to_coverage_enabled )
		{
			sun::detail::set_state( GL_SAMPLE_ALPHA_TO_COVERAGE, alpha_to_coverage_enabled );
		}

		if (	src_colour != other.src_colour
			||	src_alpha != other.src_alpha
			||	dst_colour != other.dst_colour
			||	dst_alpha != other.dst_alpha )
		{
			glBlendFuncSeparate(	src_colour,
									dst_colour,
									src_alpha,
									dst_alpha );
		}

		if (	colour_equation != other.colour_equation
			||	alpha_equation != other.alpha_equation )
		{
			glBlendEquationSeparate( colour_equation, alpha_equation );
		}

		if ( colour_mask != other.colour_mask )
		{
			glColorMask(	channel_mask::is_channel_active( colour_mask, channel_mask::red ) ? GL_TRUE : GL_FALSE,
							channel_mask::is_channel_active( colour_mask, channel_mask::green ) ? GL_TRUE : GL_FALSE,
							channel_mask::is_channel_active( colour_mask, channel_mask::blue ) ? GL_TRUE : GL_FALSE,
							channel_mask::is_channel_active( colour_mask, channel_mask::alpha ) ? GL_TRUE : GL_FALSE );
		}
	}

	/**
	Exposes the blend_state to lua.
	@param L The Lua State being initialised.
	\author Sean Tromans.
	*/
	void blend_state::lua_interface( lua_State * L )
	{
#if !defined(SUN_DISABLE_LUA)

		luabind::module( L, "rainbow" )
		[
			luabind::namespace_( "channel_mask" )
			[
				luabind::class_< channel_mask::detail::dummy_channel_enum_container >( "dummy_channel_enum_container" )
					.enum_( "constants" )
					[
						luabind::value( "red", channel_mask::red ),
						luabind::value( "green", channel_mask::green ),
						luabind::value( "blue", channel_mask::blue ),
						luabind::value( "alpha", channel_mask::alpha )
					],

				luabind::def( "set_channel", &channel_mask::set_channel ),
				luabind::def( "colour_mask", &channel_mask::colour_mask ),
				luabind::def( "is_channel_active", &channel_mask::is_channel_active )
			],

			luabind::class_< blend_state >( "blend_state" )
				.def( luabind::constructor<>( ) )
				.def_readwrite( "enabled", &blend_state::enabled )
				.def_readwrite( "alpha_to_coverage_enabled", &blend_state::alpha_to_coverage_enabled )

				.def_readwrite( "src_colour", &blend_state::src_colour )
				.def_readwrite( "dst_colour", &blend_state::dst_colour )

				.def_readwrite( "src_alpha", &blend_state::src_alpha )
				.def_readwrite( "dst_alpha", &blend_state::dst_alpha )

				.def_readwrite( "colour_equation", &blend_state::colour_equation )
				.def_readwrite( "alpha_equation", &blend_state::alpha_equation )

				.def_readwrite( "colour_mask", &blend_state::colour_mask )
		];
#else
		NMN_UNUSED( L );
#endif
	}

	/**
	Comparrison operator for blend_states.
	@param lhs The first blend_state being compared.
	@param rhs The second blend_state being compared.
	@return True if the blend_states are equal.
	\author Sean Tromans
	*/
	bool operator == ( blend_state const & lhs, blend_state const & rhs )
	{
		return	lhs.enabled == rhs.enabled
			&&	lhs.alpha_to_coverage_enabled == rhs.alpha_to_coverage_enabled
			&&	lhs.src_colour == rhs.src_colour
			&&	lhs.dst_colour == rhs.dst_colour
			&&	lhs.src_alpha == rhs.dst_alpha
			&&	lhs.colour_equation == rhs.colour_equation
			&&	lhs.alpha_equation == rhs.alpha_equation
			&&	lhs.colour_mask == rhs.colour_mask;
	}

}
